/*
 * Copyright (c) 2015. Hope6537 The Founder of Lab.JiChuang ,ChangChun University,
 * JiLin Province,China
 * JiChuang CloudStroage is a maven webapp using Hadoop Distributed File System for storage ' s Cloud Stroage System
 */

(function (factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define([], factory);
    } else if (typeof module === 'object') {
        // Node js environment
        var HashMap = module.exports = factory();
        // Keep it backwards compatible
        HashMap.HashMap = HashMap;
    } else {
        // Browser globals (this is window)
        this.HashMap = factory();
    }
}(function () {

    function HashMap(other) {
        this.clear();
        switch (arguments.length) {
            case 0:
                break;
            case 1:
                this.copy(other);
                break;
            default:
                multi(this, arguments);
                break;
        }
    }

    var proto = HashMap.prototype = {
        constructor: HashMap,

        get: function (key) {
            var data = this._data[this.hash(key)];
            return data && data[1];
        },

        set: function (key, value) {
            // Store original key as well (for iteration)
            this._data[this.hash(key)] = [key, value];
        },

        multi: function () {
            multi(this, arguments);
        },

        copy: function (other) {
            for (var key in other._data) {
                this._data[key] = other._data[key];
            }
        },

        has: function (key) {
            return this.hash(key) in this._data;
        },

        search: function (value) {
            for (var key in this._data) {
                if (this._data[key][1] === value) {
                    return this._data[key][0];
                }
            }

            return null;
        },

        remove: function (key) {
            delete this._data[this.hash(key)];
        },

        type: function (key) {
            var str = Object.prototype.toString.call(key);
            var type = str.slice(8, -1).toLowerCase();
            // Some browsers yield DOMWindow for null and undefined, works fine on Node
            if (type === 'domwindow' && !key) {
                return key + '';
            }
            return type;
        },

        keys: function () {
            var keys = [];
            this.forEach(function (value, key) {
                keys.push(key);
            });
            return keys;
        },

        values: function () {
            var values = [];
            this.forEach(function (value) {
                values.push(value);
            });
            return values;
        },

        count: function () {
            return this.keys().length;
        },

        clear: function () {
            // TODO: Would Object.create(null) make any difference
            this._data = {};
        },

        clone: function () {
            return new HashMap(this);
        },

        hash: function (key) {
            switch (this.type(key)) {
                case 'undefined':
                case 'null':
                case 'boolean':
                case 'number':
                case 'regexp':
                    return key + '';

                case 'date':
                    return ':' + key.getTime();

                case 'string':
                    return '"' + key;

                case 'array':
                    var hashes = [];
                    for (var i = 0; i < key.length; i++) {
                        hashes[i] = this.hash(key[i]);
                    }
                    return '[' + hashes.join('|');

                default:
                    // TODO: Don't use expandos when Object.defineProperty is not available?
                    if (!key._hmuid_) {
                        key._hmuid_ = ++HashMap.uid;
                        hide(key, '_hmuid_');
                    }

                    return '{' + key._hmuid_;
            }
        },

        forEach: function (func) {
            for (var key in this._data) {
                var data = this._data[key];
                func.call(this, data[1], data[0]);
            }
        }
    };

    HashMap.uid = 0;

    //- Automatically add chaining to some methods

    for (var method in proto) {
        // Skip constructor, valueOf, toString and any other built-in method
        if (method === 'constructor' || !proto.hasOwnProperty(method)) {
            continue;
        }
        var fn = proto[method];
        if (fn.toString().indexOf('return ') === -1) {
            proto[method] = chain(fn);
        }
    }

    //- Utils

    function multi(map, args) {
        for (var i = 0; i < args.length; i += 2) {
            map.set(args[i], args[i + 1]);
        }
    }

    function chain(fn) {
        return function () {
            fn.apply(this, arguments);
            return this;
        };
    }

    function hide(obj, prop) {
        // Make non iterable if supported
        if (Object.defineProperty) {
            Object.defineProperty(obj, prop, {enumerable: false});
        }
    }

    return HashMap;
}));